#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include "libhash.h"
#include "device_demo.h"
#include "libprotocol.h"
#include "link_socket.h"
#include "zlog.h"
#include "sqlite3.h"
#include "device_pth.h"

pthread_mutex_t dataBaseMutex;

CircularBuffer strainCircBuf1;
CircularBuffer strainCircBuf2;
bool strainBufFlag;
bool strainRecordFlag;

CircularBuffer vibrationCircBuf1;
CircularBuffer vibrationCircBuf2;
bool vibrationBufFlag;
bool vibrationRecordFlag;

int nodeCircBufInit(void)
{
  int ret = 0;

  strainCircBuf1 = CircularBufferCreate(STRAIN_BUF_SIZE);
  if(!strainCircBuf1){
    ret = -1;
    zlog_error(gZlog, "strain circ buffer[1] create failed");
  }

  strainCircBuf2 = CircularBufferCreate(STRAIN_BUF_SIZE);
  if(!strainCircBuf2){
    ret = -1;
    zlog_error(gZlog, "strain circ buffer[2] create failed");
  }

  vibrationCircBuf1 = CircularBufferCreate(VIBRATION_BUF_SIZE);
  if(!vibrationCircBuf1){
    ret = -1;
    zlog_error(gZlog, "vibration circ buffer[1] create failed");
  }

  vibrationCircBuf2 = CircularBufferCreate(VIBRATION_BUF_SIZE);
  if(!vibrationCircBuf2){
    ret = -1;
    zlog_error(gZlog, "vibration circ buffer[2] create failed");
  }

  return ret;
}

sqlite3 *gDataBase;

static int sqlCallback(void *NotUsed, int argc, char **argv, char **azColName){
   int i;
   for(i=0; i<argc; i++){
      //zlog_info(gZlog, "%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
   }
   return 0;
}

int acqTerDataTableInit(const char* dbPath)
{
  int ret = -1;
  char *zErrMsg = NULL;
  struct timeval now;
  struct tm fmtTime = {0};
  char sqltPathName[64] = {0};
  ini_get_string(iniConfigNode, "acqterinfo", "dataPath", sqltPathName);
  if(!dbPath){
    gettimeofday(&now, NULL);
    localtime_r(&(now.tv_sec), &fmtTime);
    strftime(&sqltPathName[strlen(sqltPathName)], sizeof(sqltPathName), "%Y-%m-%d_%H.%M.%S", &fmtTime);
    sprintf(&sqltPathName[strlen(sqltPathName)], ".db");
    dbPath = sqltPathName;
    zlog_info(gZlog, "data base path[%s]", dbPath);
  }
  //1. open database file
  ret = sqlite3_open(dbPath, &gDataBase);
  if(ret){
    zlog_error(gZlog, "open database[%s] failed", dbPath);
  }
  //2. synchronous = OFF 
  sqlite3_exec(gDataBase, "PRAGMA synchronous = OFF", 0, 0, 0);
  //3. begin commit
  sqlite3_exec(gDataBase, "BEGIN", 0, 0, 0);
  //4. create datebase table for acq terminal
  char *sqlCmd;
  sqlCmd = "CREATE TABLE STRAIN("  \
        "ID INT PRIMARY KEY   NOT NULL," \
        "DEVID			    TEXT  NOT NULL," \
        "TIME           TEXT	NOT NULL," \
		    "AD1            REAL	NOT NULL," \
		    "AD2            REAL  NOT NULL," \
		    "STRAINVAL      REAL  NOT NULL);";
  ret = sqlite3_exec(gDataBase, sqlCmd, sqlCallback, 0, &zErrMsg);
  if( ret != SQLITE_OK ){
    zlog_error(gZlog, "create table[%d] into datebase[%s] failed", "STRAIN", dbPath);
  }
  sqlCmd = "CREATE TABLE VIBRATION("  \
        "ID INT PRIMARY KEY   NOT NULL," \
        "DEVID			    TEXT  NOT NULL," \
        "TIME           TEXT	NOT NULL," \
		    "TEMP           REAL	NOT NULL," \
		    "ACCX           REAL	NOT NULL," \
		    "ACCY           REAL	NOT NULL," \
		    "ACCZ           REAL	NOT NULL," \
		    "PITCH          REAL	NOT NULL," \
		    "ROLL           REAL  NOT NULL," \
		    "YAW            REAL  NOT NULL);";
  ret = sqlite3_exec(gDataBase, sqlCmd, sqlCallback, 0, &zErrMsg);
  if( ret != SQLITE_OK ){
    zlog_error(gZlog, "create table[%d] into datebase[%s] failed", "STRAIN", dbPath);
  }
  return 0;
}

unsigned long int gStrainIndex, gVibrationIndex;

int recordStrainInfo(strainInfo_t strainInfo)
{
  int ret = -1;
  char *zErrMsg = NULL;
  gStrainIndex ++;
  //1. struct timeval to time string
  struct tm fmtTime = {0};
  localtime_r(&(strainInfo.timeStamp.tv_sec), &fmtTime);
  char timeStampStr[32] = {0};
  strftime(timeStampStr, sizeof(timeStampStr), "%Y-%m-%d %H:%M:%S", &fmtTime);
  sprintf(&timeStampStr[19], ".%ld", strainInfo.timeStamp.tv_usec / 1000);
  //2. sqlite cmd
  char sqlCmd[256] = {0};
  sprintf(sqlCmd, "INSERT INTO STRAIN (ID,DEVID,TIME,AD1,AD2,STRAINVAL) "  \
         "VALUES (%d, 'acqNode[%d]', '%s', %.4f, %.4f, %.4f);", gStrainIndex, \
		 strainInfo.devId, timeStampStr, strainInfo.strainAdCh1, strainInfo.strainAdCh2, \
		 strainInfo.strainVal);
  if((gStrainIndex % 1000) == 0){
    sqlite3_exec(gDataBase, "COMMIT", 0, 0, 0);
    sqlite3_exec(gDataBase, "BEGIN", 0, 0, 0);
  }
  //3. exec sel cmd
  ret = sqlite3_exec(gDataBase, sqlCmd, sqlCallback, 0, &zErrMsg);
  if( ret != SQLITE_OK ){
    zlog_error(gZlog, "insert data into table[%d] failed, ret[%d]", "STRAIN", ret);
    return ret;
  }
  return 0;
}

int recordVibrationInfo(vibrationInfo_t vibrationInfo)
{
  int ret = -1;
  char *zErrMsg = NULL;
  gVibrationIndex ++;
  //1. struct timeval to time string
  struct tm fmtTime = {0};
  localtime_r(&(vibrationInfo.timeStamp.tv_sec), &fmtTime);
  char timeStampStr[32] = {0};
  strftime(timeStampStr, sizeof(timeStampStr), "%Y-%m-%d %H:%M:%S", &fmtTime);
  sprintf(&timeStampStr[19], ".%ld", vibrationInfo.timeStamp.tv_usec / 1000);
  //2. sqlite cmd
  char sqlCmd[256] = {0};
  sprintf(sqlCmd, "INSERT INTO VIBRATION (ID,DEVID,TIME,TEMP,ACCX,ACCY,ACCZ,PITCH,ROLL,YAW) "  \
         "VALUES (%d, 'acqNode[%d]', '%s', %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %4.f);", gVibrationIndex, \
		 vibrationInfo.devId, timeStampStr, vibrationInfo.temperature, vibrationInfo.accSpeedX, vibrationInfo.accSpeedY, \
		 vibrationInfo.accSpeedZ, vibrationInfo.angleX, vibrationInfo.angleY, vibrationInfo.angleZ);
  if((gVibrationIndex % 1000) == 0){
    sqlite3_exec(gDataBase, "COMMIT", 0, 0, 0);
    sqlite3_exec(gDataBase, "BEGIN", 0, 0, 0);
  }
  //3. exec sel cmd
  ret = sqlite3_exec(gDataBase, sqlCmd, sqlCallback, 0, &zErrMsg);
  if( ret != SQLITE_OK ){
    zlog_error(gZlog, "insert data int table[%d] failed, ret[%d]", "VIBRATION");
    return ret;
  }
  return 0;
}

int devRecordDataPth(int nodeDataCnt)
{
  struct timeval now;
  struct tm fmtTime = {0};
  char sqltPathName[64] = {0};
  char sqltPathNameLast[64] = {0};
  char sqltPathNamePrefix[32] = {0};
  unsigned int recordStrainDataCnt = 0;
  unsigned int recordVibrationDataCnt = 0;
  ini_get_string(iniConfigNode, "acqterinfo", "dataPath", sqltPathNamePrefix);
  while(1){
    // shift record every one hour
    gettimeofday(&now, NULL);
    localtime_r(&(now.tv_sec), &fmtTime);
    memset(sqltPathName, 0, sizeof(sqltPathName));
    strncpy(sqltPathName, sqltPathNamePrefix, strlen(sqltPathNamePrefix));
    strftime(&sqltPathName[strlen(sqltPathNamePrefix)], sizeof(sqltPathName), "%Y-%m-%d_%H.%M.%S", &fmtTime);
    sprintf(&sqltPathName[strlen(sqltPathName)], ".db");
    if(fmtTime.tm_min%10 == 0 && fmtTime.tm_sec == 0 && (0 != strncmp(sqltPathName, sqltPathNameLast, strlen(sqltPathName)))){
      zlog_info(gZlog, "data base[%s] shift record, close database fd", sqltPathName);
      sqlite3_exec(gDataBase, "COMMIT", 0, 0, 0);
      sqlite3_close(gDataBase);
      zlog_info(gZlog, "record data base[%s]", sqltPathName);
      acqTerDataTableInit(sqltPathName);
      memset(sqltPathNameLast, 0, sizeof(sqltPathNameLast));
      strncpy(sqltPathNameLast, sqltPathName, sizeof(sqltPathName));
    }
    // record strain data
    strainInfo_t strainData;
   // zlog_info(gZlog, "strain record flag[%d], strain buffer flag[%d]", strainRecordFlag, strainBufFlag);
    if(strainRecordFlag){// have data
      if(strainBufFlag){// from strainCircBuf1
        for(int i = 0; i < nodeDataCnt; i++){
          CircularBufferPop(strainCircBuf1, sizeof(strainInfo_t), &strainData);
          recordStrainInfo(strainData);
          recordStrainDataCnt ++;
        }
        strainRecordFlag = false;
      }else{// from strainCircBuf2
        for(int j = 0; j < nodeDataCnt; j++){
          CircularBufferPop(strainCircBuf2, sizeof(strainInfo_t), &strainData);
          recordStrainInfo(strainData);
          recordStrainDataCnt ++;
        }
        strainRecordFlag = false;
      }
      zlog_info(gZlog, "record strain data count[%d]", recordStrainDataCnt);
    }
    // record vibration data
    vibrationInfo_t vibrationData;
    if(vibrationRecordFlag){// have data
      if(vibrationBufFlag){// from vibrationCircBuf1
        for(int i = 0; i < nodeDataCnt; i++){
          CircularBufferPop(vibrationCircBuf1, sizeof(vibrationInfo_t), &vibrationData);
          recordVibrationInfo(vibrationData);
          recordVibrationDataCnt++;
        }
        vibrationRecordFlag = false;
      }else{// from strainCircBuf2
        for(int j = 0; j < nodeDataCnt; j++){
          CircularBufferPop(vibrationCircBuf2, sizeof(vibrationInfo_t), &vibrationData);
          recordVibrationInfo(vibrationData);
          recordVibrationDataCnt++;
        }
        vibrationRecordFlag = false;
      }
      zlog_info(gZlog, "record vibration data count[%d]", recordVibrationDataCnt);
    }
  }
  return 0;
}

int devRecordDataPthInit(void)
{
  pthread_t recordPth;
  int nodeDataCnt = 1;
  ini_get_int(iniConfigNode, "acqterinfo", "acqDataCnt", &nodeDataCnt);
  if (pthread_create(&recordPth, NULL, (void *)devRecordDataPth, nodeDataCnt) != 0){
    zlog_error(gZlog, "Failed to create devRecordPth");
    return -1;
  }
  pthread_detach(recordPth);
  return 0;
}

int devCommPth(int devNumCfg)
{
  int             ret             = -1;
  int             devCntFind      = 0;
  int             nodeDataCnt     = 1;
  int             strainNodeCnt   = 14;
  linkList_t      *crtLink        = NULL;
  bool            syncTimeFlag    = false;
  strainInfo_t    *strainInfo     = NULL;
  vibrationInfo_t *vibrationInfo  = NULL;
  unsigned int    disConThreshold = 5;
  unsigned int    disCardThreshold= 5;
  
  ini_get_int(iniConfigNode, "acqterinfo", "acqDataCnt", &nodeDataCnt);
  ini_get_int(iniConfigNode, "acqterinfo", "strainNodeNum", &strainNodeCnt);
  ini_get_int(iniConfigNode, "acqterinfo", "disConthreshold", &disConThreshold);
  if(disConThreshold < 1){
    disConThreshold = 1;
  }
  ini_get_int(iniConfigNode, "acqterinfo", "disCardThreshold", &disCardThreshold);
  if(disCardThreshold < 1){
    disCardThreshold = 1;
  }
  zlog_info(gZlog, "\n[%d] acqNodes config to the system, node data count[%d]", devNumCfg, nodeDataCnt);
  strainInfo    = (strainInfo_t *)malloc(nodeDataCnt * sizeof(strainInfo_t));
  vibrationInfo = (vibrationInfo_t *)malloc(nodeDataCnt * sizeof(vibrationInfo_t));
  nodeCircBufInit();
  while(1){// main loop
    devCntFind = sizeOfLinkList(gServerLinkHead);
    if(devCntFind == 0){
      zlog_error(gZlog, "except [%d] acqNodes but [%d] acqNodes are found", devNumCfg, devCntFind);
    }else if(devCntFind != devNumCfg){
      zlog_error(gZlog, "except [%d] acqNodes but [%d] acqNodes are found", devNumCfg, devCntFind);
    }

    for(int i = 1; i <= devNumCfg; i++){
      int devId = i;
      int devIndex = i - 1;
      if(devInfoArray[devIndex].status > INVALID){
        crtLink = findLinkNodeFromList(gServerLinkHead, devId);
        if(crtLink){
          zlog_debug(gZlog, "find devId[%d]", crtLink->link.linkId);
          if(!syncTimeFlag){
            ret = requirSyncTime(devId);
            ret = requirSetNodeType(devId, STRAIN_NODE, nodeDataCnt);
          }
          if(devId <= strainNodeCnt){
            ret = requirGetStrain(devId, &strainInfo, nodeDataCnt);
            zlog_info(gZlog, "dev[%d],ret[%d], commErrCnt=[%d]", devId, ret, devInfoArray[devIndex].commErrCnt);
            if(ret != 0){
              devInfoArray[devIndex].commErrCnt ++;
              zlog_info(gZlog, "dev[%d].commErrCnt=[%d]", devId, devInfoArray[devIndex].commErrCnt);
              if(devInfoArray[devIndex].commErrCnt >= disConThreshold){
                delDev(devId);
                if((devInfoArray[devIndex].disConCnt ++) < disCardThreshold){
                  zlog_info(gZlog, "dev[%d].disConCnt=[%d]", devId, devInfoArray[devIndex].disConCnt);
                  usleep(10000);
                  addDev(i, devInfoArray[devIndex]);
                }else{
                  devInfoArray[devIndex].status = INVALID;
                }
              }
            }
          }else{
            ret = requirGetVibration(i, &vibrationInfo, nodeDataCnt);
            if(ret < 0){
              devInfoArray[devIndex].commErrCnt ++;
              if(devInfoArray[devIndex].commErrCnt >= disConThreshold){
                delDev(devId);
                if((devInfoArray[devIndex].disConCnt ++) < disCardThreshold){
                  usleep(10000);
                  addDev(i, devInfoArray[devIndex]);
                }else{
                  devInfoArray[devIndex].status = INVALID;
                }
              }
            }
          }
        }
      }
    }
    syncTimeFlag = true;
  }
  return ret;
}

int devCommPthInit(int devNum)
{
  pthread_t commPth;
    if (pthread_create(&commPth, NULL, (void *)devCommPth, devNum) != 0){
        zlog_error(gZlog, "Failed to create devCommPth");
        return -1;
    }
    pthread_detach(commPth);
    return 0;
}

